// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import '../types/shared_type.dart';
import 'type_analyzer.dart';

/// Result for analyzing an assigned variable pattern in
/// [TypeAnalyzer.analyzeAssignedVariablePattern].
class AssignedVariablePatternResult<Error> extends PatternResult {
  /// Error for when a variable was assigned multiple times within a pattern.
  final Error? duplicateAssignmentPatternVariableError;

  /// Error for when the matched value type is not assignable to the variable
  /// type in an irrefutable context.
  final Error? patternTypeMismatchInIrrefutableContextError;

  AssignedVariablePatternResult({
    required this.duplicateAssignmentPatternVariableError,
    required this.patternTypeMismatchInIrrefutableContextError,
    required super.matchedValueType,
  });
}

/// Result for analyzing a constant pattern in
/// [TypeAnalyzer.analyzeConstantPattern].
class ConstantPatternResult<Error> extends PatternResult {
  /// The static type of the constant expression.
  final SharedTypeView expressionType;

  /// Error for when the pattern occurred in an irrefutable context.
  final Error? refutablePatternInIrrefutableContextError;

  /// Error for when the pattern, used as a case constant expression, does not
  /// have a valid type wrt. the switch expression type.
  final Error? caseExpressionTypeMismatchError;

  ConstantPatternResult({
    required this.expressionType,
    required this.refutablePatternInIrrefutableContextError,
    required this.caseExpressionTypeMismatchError,
    required super.matchedValueType,
  });
}

/// Result for analyzing a declared variable pattern in
/// [TypeAnalyzer.analyzeDeclaredVariablePattern].
class DeclaredVariablePatternResult<Error> extends PatternResult {
  /// The static type of the variable.
  final SharedTypeView staticType;

  /// Error for when the matched value type is not assignable to the static
  /// type in an irrefutable context.
  final Error? patternTypeMismatchInIrrefutableContextError;

  DeclaredVariablePatternResult({
    required this.staticType,
    required this.patternTypeMismatchInIrrefutableContextError,
    required super.matchedValueType,
  });
}

/// Container for the result of running type analysis on an expression.
///
/// This class keeps track of the type of the expression. Derived classes expose
/// other results of type analysis that are specific to certain expression
/// types.
class ExpressionTypeAnalysisResult {
  /// The static type of the expression.
  final SharedTypeView type;

  ExpressionTypeAnalysisResult({required this.type});
}

/// Result for analyzing an if-case statement or element in
/// [TypeAnalyzer.analyzeIfCaseStatement] and
/// [TypeAnalyzer.analyzeIfCaseElement].
class IfCaseStatementResult<Error> {
  /// The static type of the matched expression.
  final SharedTypeView matchedExpressionType;

  /// Error for when the guard has a non-bool type.
  final Error? nonBooleanGuardError;

  /// The type of the guard expression, if present.
  final SharedTypeView? guardType;

  IfCaseStatementResult({
    required this.matchedExpressionType,
    required this.nonBooleanGuardError,
    required this.guardType,
  });
}

/// Container for the result of running type analysis on an integer literal.
class IntTypeAnalysisResult extends ExpressionTypeAnalysisResult {
  /// Whether the integer literal was converted to a double.
  final bool convertedToDouble;

  IntTypeAnalysisResult({required super.type, required this.convertedToDouble});
}

/// Result for analyzing a list pattern in [TypeAnalyzer.analyzeListPattern].
class ListPatternResult<Error> extends PatternResult {
  /// The required type of the list pattern.
  final SharedTypeView requiredType;

  /// Errors for when multiple rest patterns occurred within the list pattern.
  ///
  /// The key is the index of the pattern within the list pattern.
  ///
  /// This is `null` if no such errors where found.
  final Map<int, Error>? duplicateRestPatternErrors;

  /// Error for when the matched value type is not assignable to the required
  /// type in an irrefutable context.
  final Error? patternTypeMismatchInIrrefutableContextError;

  ListPatternResult({
    required this.requiredType,
    required this.duplicateRestPatternErrors,
    required this.patternTypeMismatchInIrrefutableContextError,
    required super.matchedValueType,
  });
}

/// Result for analyzing a logical or pattern in
/// [TypeAnalyzer.analyzeLogicalOrPattern].
class LogicalOrPatternResult<Error> extends PatternResult {
  /// Error for when the pattern occurred in an irrefutable context.
  final Error? refutablePatternInIrrefutableContextError;

  LogicalOrPatternResult({
    required this.refutablePatternInIrrefutableContextError,
    required super.matchedValueType,
  });
}

/// Result for analyzing a map pattern in [TypeAnalyzer.analyzeMapPattern].
class MapPatternResult<Error> extends PatternResult {
  /// The required type of the map pattern.
  final SharedTypeView requiredType;

  /// Error for when the matched value type is not assignable to the required
  /// type in an irrefutable context.
  final Error? patternTypeMismatchInIrrefutableContextError;

  /// Error for when the map pattern is empty.
  final Error? emptyMapPatternError;

  /// Errors for when the map pattern contains rest patterns.
  ///
  /// The key is the indices it which the rest pattern occurred.
  final Map<int, Error>? restPatternErrors;

  MapPatternResult({
    required this.requiredType,
    required this.patternTypeMismatchInIrrefutableContextError,
    required this.emptyMapPatternError,
    required this.restPatternErrors,
    required super.matchedValueType,
  });
}

/// Information about the code context surrounding a pattern match.
class MatchContext<
  Node extends Object,
  Expression extends Node,
  Pattern extends Node,
  Type extends Object,
  Variable extends Object
> {
  /// If non-`null`, the match is being done in an irrefutable context, and this
  /// is the surrounding AST node that establishes the irrefutable context.
  final Node? irrefutableContext;

  /// Indicates whether variables declared in the pattern should be `final`.
  final bool isFinal;

  /// The switch scrutinee, or `null` if this pattern does not occur in a switch
  /// statement or switch expression, or this pattern is not the top-level
  /// pattern.
  final Expression? switchScrutinee;

  /// If the match is being done in a pattern assignment, the set of variables
  /// assigned so far.
  final Map<Variable, Pattern>? assignedVariables;

  /// For each variable name in the pattern, a list of the variables which might
  /// capture that variable's value, depending upon which alternative is taken
  /// in a logical-or pattern.
  final Map<String, List<Variable>> componentVariables;

  /// For each variable name in the pattern, the promotion key holding the value
  /// captured by that variable.
  final Map<String, int> patternVariablePromotionKeys;

  /// If non-null, the warning that should be issued if the pattern is `_`
  final UnnecessaryWildcardKind? unnecessaryWildcardKind;

  MatchContext({
    this.irrefutableContext,
    required this.isFinal,
    this.switchScrutinee,
    this.assignedVariables,
    required this.componentVariables,
    required this.patternVariablePromotionKeys,
    this.unnecessaryWildcardKind,
  });

  /// Returns a modified version of `this`, with [irrefutableContext] set to
  /// `null`.  This is used to suppress cascading errors after reporting
  /// [TypeAnalyzerErrors.refutablePatternInIrrefutableContext].
  MatchContext<Node, Expression, Pattern, Type, Variable> makeRefutable() =>
      irrefutableContext == null
      ? this
      : new MatchContext(
          isFinal: isFinal,
          switchScrutinee: switchScrutinee,
          assignedVariables: assignedVariables,
          componentVariables: componentVariables,
          patternVariablePromotionKeys: patternVariablePromotionKeys,
        );

  /// Returns a modified version of `this`, with a new value of
  /// [patternVariablePromotionKeys].
  MatchContext<Node, Expression, Pattern, Type, Variable> withPromotionKeys(
    Map<String, int> patternVariablePromotionKeys,
  ) => new MatchContext(
    irrefutableContext: irrefutableContext,
    isFinal: isFinal,
    switchScrutinee: null,
    assignedVariables: assignedVariables,
    componentVariables: componentVariables,
    patternVariablePromotionKeys: patternVariablePromotionKeys,
    unnecessaryWildcardKind: unnecessaryWildcardKind,
  );

  /// Returns a modified version of `this`, with [switchScrutinee] set to `null`
  /// (because this context is not for a top-level pattern anymore).
  MatchContext<Node, Expression, Pattern, Type, Variable>
  withUnnecessaryWildcardKind(
    UnnecessaryWildcardKind? unnecessaryWildcardKind,
  ) {
    return new MatchContext(
      irrefutableContext: irrefutableContext,
      isFinal: isFinal,
      assignedVariables: assignedVariables,
      switchScrutinee: null,
      componentVariables: componentVariables,
      patternVariablePromotionKeys: patternVariablePromotionKeys,
      unnecessaryWildcardKind: unnecessaryWildcardKind,
    );
  }
}

/// Result for analyzing a null check or null assert pattern in
/// [TypeAnalyzer.analyzeNullCheckOrAssertPattern].
class NullCheckOrAssertPatternResult<Error> extends PatternResult {
  /// Error for when the pattern occurred in an irrefutable context.
  final Error? refutablePatternInIrrefutableContextError;

  /// Error for when the matched type is known to be non-null.
  final Error? matchedTypeIsStrictlyNonNullableError;

  NullCheckOrAssertPatternResult({
    required this.refutablePatternInIrrefutableContextError,
    required this.matchedTypeIsStrictlyNonNullableError,
    required super.matchedValueType,
  });
}

/// Result for analyzing an object pattern in
/// [TypeAnalyzer.analyzeObjectPattern].
class ObjectPatternResult<Error> extends PatternResult {
  /// The required type of the object pattern.
  final SharedTypeView requiredType;

  /// Errors for when the same property name was used multiple times in the
  /// object pattern.
  ///
  /// The key is the index of the duplicate field within the object pattern.
  ///
  /// This is `null` if no such properties were found.
  final Map<int, Error>? duplicateRecordPatternFieldErrors;

  /// Error for when the matched value type is not assignable to the required
  /// type in an irrefutable context.
  final Error? patternTypeMismatchInIrrefutableContextError;

  ObjectPatternResult({
    required this.requiredType,
    required this.duplicateRecordPatternFieldErrors,
    required this.patternTypeMismatchInIrrefutableContextError,
    required super.matchedValueType,
  });
}

/// Container for the result of running type analysis on a pattern assignment.
class PatternAssignmentAnalysisResult extends ExpressionTypeAnalysisResult {
  /// The type schema of the pattern on the left hand size of the assignment.
  final SharedTypeSchemaView patternSchema;

  PatternAssignmentAnalysisResult({
    required this.patternSchema,
    required super.type,
  });
}

/// Result for analyzing a pattern-for-in statement or element in
/// [TypeAnalyzer.analyzePatternForIn].
class PatternForInResult<Error> {
  /// The static type of the elements of the for in expression.
  final SharedTypeView elementType;

  /// The static type of the collection of elements of the for in expression.
  final SharedTypeView expressionType;

  /// Error for when the expression is not an iterable.
  final Error? patternForInExpressionIsNotIterableError;

  PatternForInResult({
    required this.elementType,
    required this.expressionType,
    required this.patternForInExpressionIsNotIterableError,
  });
}

/// Result for analyzing a pattern in [TypeAnalyzer].
class PatternResult {
  /// The matched value type that was used to type check the pattern.
  final SharedTypeView matchedValueType;

  PatternResult({required this.matchedValueType});
}

/// Container for the result of running type analysis on a pattern variable
/// declaration.
class PatternVariableDeclarationAnalysisResult {
  /// The type schema of the pattern on the left hand size of the declaration.
  final SharedTypeSchemaView patternSchema;

  /// The type of the initializer expression.
  final SharedTypeView initializerType;

  PatternVariableDeclarationAnalysisResult({
    required this.patternSchema,
    required this.initializerType,
  });
}

/// Result for analyzing a record pattern in
/// [TypeAnalyzer.analyzeRecordPattern].
class RecordPatternResult<Error> extends PatternResult {
  /// The required type of the record pattern.
  final SharedTypeView requiredType;

  /// Errors for when the same property name was used multiple times in the
  /// record pattern.
  ///
  /// The key is the index of the duplicate field within the record pattern.
  ///
  /// This is `null` if no such errors where found.
  final Map<int, Error>? duplicateRecordPatternFieldErrors;

  /// Error for when the matched value type is not assignable to the required
  /// type in an irrefutable context.
  final Error? patternTypeMismatchInIrrefutableContextError;

  RecordPatternResult({
    required this.requiredType,
    required this.duplicateRecordPatternFieldErrors,
    required this.patternTypeMismatchInIrrefutableContextError,
    required super.matchedValueType,
  });
}

/// Result for analyzing a relational pattern in
/// [TypeAnalyzer.analyzeRelationalPattern].
class RelationalPatternResult<Error> extends PatternResult {
  /// The static type of the operand.
  final SharedTypeView operandType;

  /// Error for when the pattern occurred in an irrefutable context.
  final Error? refutablePatternInIrrefutableContextError;

  /// Error for when the operand type is not assignable to the parameter type
  /// of the relational operator.
  final Error? argumentTypeNotAssignableError;

  /// Error for when the relational operator does not return a bool.
  final Error? operatorReturnTypeNotAssignableToBoolError;

  RelationalPatternResult({
    required this.operandType,
    required this.refutablePatternInIrrefutableContextError,
    required this.argumentTypeNotAssignableError,
    required this.operatorReturnTypeNotAssignableToBoolError,
    required super.matchedValueType,
  });
}

/// Result for analyzing a switch expression in
/// [TypeAnalyzer.analyzeSwitchExpression].
class SwitchExpressionResult<Error> extends ExpressionTypeAnalysisResult {
  /// Errors for non-bool guards.
  ///
  /// The key is the case index of the erroneous guard.
  ///
  /// This is `null` if no such errors where found.
  final Map<int, Error>? nonBooleanGuardErrors;

  /// The types of the guard expressions.
  ///
  /// The key is the case index of the guard.
  ///
  /// This is `null` if no such guards where present.
  final Map<int, SharedTypeView>? guardTypes;

  SwitchExpressionResult({
    required super.type,
    required this.nonBooleanGuardErrors,
    required this.guardTypes,
  });
}

/// Container for the result of running type analysis on an integer literal.
class SwitchStatementTypeAnalysisResult<Error> {
  /// Whether the switch statement had a `default` clause.
  final bool hasDefault;

  /// Whether the switch statement was exhaustive.
  final bool isExhaustive;

  /// Whether the last case body in the switch statement terminated.
  final bool lastCaseTerminates;

  /// If `true`, patterns support is enabled, there is no default clause, and
  /// the static type of the scrutinee expression is an "always exhaustive"
  /// type.  Therefore, flow analysis has assumed (without checking) that the
  /// switch statement is exhaustive.  So at a later stage of compilation, the
  /// exhaustiveness checking algorithm should check whether this switch
  /// statement was exhaustive, and report a compile-time error if it wasn't.
  final bool requiresExhaustivenessValidation;

  /// The static type of the scrutinee expression.
  final SharedTypeView scrutineeType;

  /// Errors for the cases that don't complete normally.
  ///
  /// This is `null` if no such errors where found.
  final Map<int, Error>? switchCaseCompletesNormallyErrors;

  /// Errors for non-bool guards.
  ///
  /// The keys of the maps are case and head indices of the erroneous guard.
  ///
  /// This is `null` if no such errors where found.
  final Map<int, Map<int, Error>>? nonBooleanGuardErrors;

  /// The types of the guard expressions.
  ///
  /// The keys of the maps are case and head indices of the guard.
  ///
  /// This is `null` if no such guards where present.
  final Map<int, Map<int, SharedTypeView>>? guardTypes;

  SwitchStatementTypeAnalysisResult({
    required this.hasDefault,
    required this.isExhaustive,
    required this.lastCaseTerminates,
    required this.requiresExhaustivenessValidation,
    required this.scrutineeType,
    required this.switchCaseCompletesNormallyErrors,
    required this.nonBooleanGuardErrors,
    required this.guardTypes,
  });
}

/// The location of a wildcard pattern that was found unnecessary.
///
/// When a wildcard pattern always matches, and is not required by the
/// by the location, we can report it as unnecessary. The locations where it
/// is necessary include list patterns, record patterns, cast patterns, etc.
enum UnnecessaryWildcardKind {
  /// The wildcard pattern is the left or the right side of a logical-and
  /// pattern. Because we found that is always matches, it has no effect,
  /// and can be removed.
  logicalAndPatternOperand,
}

/// Result for analyzing a wildcard pattern
/// [TypeAnalyzer.analyzeWildcardPattern].
class WildcardPatternResult<Error> extends PatternResult {
  /// Error for when the matched value type is not assignable to the wildcard
  /// type in an irrefutable context.
  final Error? patternTypeMismatchInIrrefutableContextError;

  WildcardPatternResult({
    required this.patternTypeMismatchInIrrefutableContextError,
    required super.matchedValueType,
  });
}
